package org.iweb.app;

import groovy.util.GroovyScriptEngine;
import groovy.util.ResourceException;

import java.io.IOException;
import java.io.Serializable;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.iweb.html.api.AbstractHtmlElement;
import org.iweb.html.api.Listener;
import org.iweb.html.impl.basic.Form;
import org.iweb.html.impl.basic.Form.CustomFormBean;
import org.iweb.html.impl.basic.Script;
import org.iweb.html.impl.js.state.ElementState;
import org.iweb.html.impl.js.state.HtmlState;
import org.iweb.html.impl.session.AppSession;
import org.iweb.register.RegisterFactory;
import org.iweb.web.filter.RouteFilter;
import org.iweb.web.ui.layout.Layout;
import org.iweb.web.ui.layout.LayoutFactory;
import org.iweb.web.ui.page.Page;
import org.iweb.web.ui.page.PageFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.json.JsonHierarchicalStreamDriver;
import com.thoughtworks.xstream.io.json.JsonWriter;

@SuppressWarnings({"rawtypes","unchecked"})
public abstract class AbstractApplication implements Serializable {

	private static final long serialVersionUID = 208602000186261090L;
	
	private static GroovyScriptEngine groovyScriptEngine;
	private String companyName;
	private static XStream xstreamJson;
	private static Map<String, Class> groovyClass;

	static{
		String[] roots = new String[] {
				System.getProperty("catalina.base") + "/webapps/iweb/pages",				
				System.getProperty("catalina.base") + "/webapps/iweb/template/company"				
				};
		try {
			setGroovyScriptEngine(new GroovyScriptEngine(roots));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		groovyClass=new HashMap<String,Class>();
	}
	
	public AbstractApplication(String _companyName) {
		this.companyName=_companyName;		
	}

	public void route(HttpServletRequest request, HttpServletResponse response, AppSession appSession) {
		
		if(reload==null){
			reload=new Listener("reload-classes") {
				
				/**
				 * 
				 */
				private static final long serialVersionUID = 6027054711151724490L;

				@Override
				public void run(HttpServletRequest request, CustomFormBean formBean) {
					RegisterFactory.getInstance().reload();				
				}
			};
		}
		
		Logger log=LoggerFactory.getLogger(RouteFilter.class);
		
		String path=request.getRequestURI().replaceFirst(request.getContextPath()+"/portal", "");
		
		if(path!=null && !path.startsWith("/"))
			path="/"+path;
		
		if(path.startsWith("/actions")){
			handleActions(request,response,appSession);
			return;
		}
		
		Class c=PageFactory.getInstance().getPage(path);
		
		log.debug("From page factory -> path: "+path+" class: "+c);
		
		ServletOutputStream out=null;
		String method=request.getParameter("method")!=null?request.getParameter("method"):"renderInt";
		
		if(c!=null){
			try {
				out=response.getOutputStream();
				Page ann=(Page) c.getAnnotation(Page.class);			
				Object obj=renderGroovyClass(ann.renderMethod(), c, appSession);
				if(obj!=null){
					
					if("true".equalsIgnoreCase(request.getParameter("nolayout"))){
						//At the first ajax call it doesn't need layout
						out.write(obj.toString().getBytes());
						return;
					}
					
					Class layout=LayoutFactory.getInstance().getLayout(ann.layout());
					
					Layout annL=(Layout) layout.getAnnotation(Layout.class);
					
					obj=renderGroovyLayout(annL.renderMethod(),layout,appSession,request.getContextPath(),path);
					
					out.write(obj.toString().getBytes());
				}
			} catch (Exception e) {
				e.printStackTrace();
				if(out!=null)
					try {
						out.write(e.getMessage().getBytes());
					} catch (IOException e1) {
						e1.printStackTrace();
					}
				e.printStackTrace();
			}
			return;
		}
		
		if("/".equals(path.trim())){
			path+="Init";
		}
		
		path=getCompanyName()+path+".groovy";
		log.debug("Request URI: "+path);
		
		try {
			out=response.getOutputStream();
			Object obj=renderGroovyScript(method,path, appSession);
			if(obj!=null)				
				out.write(obj.toString().getBytes());
		} catch (IOException e) {
			if(out!=null)
				try {
					out.write(e.getMessage().getBytes());
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			e.printStackTrace();
		}
	}

	private void handleActions(HttpServletRequest request,
			HttpServletResponse response, AppSession appSession) {
		String listenerid = request.getParameter("listenerid");
		String beanid = request.getParameter("beanid");
		
		Form.CustomFormBean bean = null;
		try {
			if (beanid != null && beanid.trim().length() > 0) {
				bean = new Form.BeanFactory().getBean(
						request.getParameterMap(),
						(CustomFormBean) this.getClass().getClassLoader()
								.loadClass(beanid).newInstance());
			}
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		
		if(listenerid!=null && appSession.getObserver().getListener(listenerid)!=null){
			Listener l=appSession.getObserver().getListener(listenerid);
			l.run(request, bean);
		}
		
		response.setContentType("application/json");
		
		try {
			response.getOutputStream().print(getXstreamJson().toXML(appSession.getAppState().getHtmlState().getElstates()));
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public static GroovyScriptEngine getGroovyScriptEngine() {
		return groovyScriptEngine;
	}

	public static void setGroovyScriptEngine(GroovyScriptEngine groovyScriptEngine) {
		AbstractApplication.groovyScriptEngine = groovyScriptEngine;
	}

	public String getCompanyName() {
		return companyName;
	}

	public void setCompanyName(String companyName) {
		this.companyName = companyName;
	}

	public Object renderGroovyScript(String method,String scriptName, AppSession appSession){		 
		try {
			Class scriptClass=groovyClass.get(scriptName);
			
			if(scriptClass==null){
			
				try{					
					scriptClass = getGroovyScriptEngine().loadScriptByName(scriptName);
				}catch(ResourceException re){
					
				}
				
				if(scriptClass==null){
					try{
						scriptClass = getGroovyScriptEngine().loadScriptByName(scriptName.replaceFirst(getCompanyName()+"/", ""));
					}catch(ResourceException re){
						//provo a cercare una cartella con questo nome
						scriptClass = getGroovyScriptEngine().loadScriptByName(scriptName.replaceFirst(getCompanyName()+"/", "").replaceFirst(".groovy", "/Init.groovy"));
					}				
				}
				
				groovyClass.put(scriptName, scriptClass);
			
			}
			
			return renderGroovyClass(method, scriptClass, appSession);
			
		} catch (Exception e) {
			e.printStackTrace();
			return e.getClass().getName()+" -> "+e.getMessage();
		}
	}
	
	public Object renderGroovyClass(String method,Class scriptClass, AppSession appSession) throws Exception{

		// ?? nn ricordo il perchè
		//appSession.getGroovyScript().put(scriptName, scriptInstance);
		
		Object result = null;

		result = scriptClass.getMethod(method,
				new Class[] {}).invoke(scriptClass.newInstance(), new Object[] {});

		if(result instanceof AbstractHtmlElement){
			
			if(appSession.getAppState().getScriptElement()!=null && appSession.getAppState().getScriptElement().size()>0)
				((AbstractHtmlElement)result).append(new Script(appSession.getAppState().getScriptElement()));
			return ((AbstractHtmlElement)result).render();
		}
		
		return result;
		
	}
	
	public Object renderGroovyLayout(String method,Class scriptClass, AppSession appSession,String context,String content) throws Exception{		

		Object result = scriptClass.getMethod(method,
				new Class[] {String.class,String.class}).invoke(scriptClass.newInstance(), new String[] {context,content});

		if(result instanceof AbstractHtmlElement){
			
			if(appSession.getAppState().getScriptElement()!=null && appSession.getAppState().getScriptElement().size()>0)
				((AbstractHtmlElement)result).append(new Script(appSession.getAppState().getScriptElement()));
			return ((AbstractHtmlElement)result).render();
		}
		
		return result;
		
	}

	
	public static XStream getXstreamJson() {
		
		if(xstreamJson==null){
			xstreamJson = new XStream(new JsonHierarchicalStreamDriver() {
	
				@Override
				public HierarchicalStreamWriter createWriter(Writer out) {			
					return new JsonWriter(out, JsonWriter.DROP_ROOT_MODE);
				}
	
			});
			xstreamJson.setMode(XStream.NO_REFERENCES);
			xstreamJson.alias("htmlstate", HtmlState.class);
			xstreamJson.alias("elementstate", ElementState.class);
		}
		
		return xstreamJson;
	}
	
	private static Listener reload;
	
}
